#include "NJO.h"
/*
 * BlueMatrixClock.ino
 *
 * Created: 2/16/2015 7:57:11 PM
 * Author: Neil Openshaw
 */ 

#include <Arduino.h>
#include <Wire.h>

#ifdef PROGMEM
#undef PROGMEM
#define PROGMEM __attribute__((section(".progmem.data")))
#endif

#include "Time.h"
#include "TinyGPS.h"
#include "TinyGPS++.h"      
#include "SoftwareSerial.h"
#include "avr/interrupt.h"
#include "Timezone.h"
//#include <Adafruit_GPS.h>
#include "NJO_ht16k33.h"
#include "NJO_Matrix_font1.h"
#include "OneWire.h"
#include "DallasTemperature.h"
#include "ClockTimeZones.h"
#include "QueueArray.h"

typedef void (*function) () ;

// Buttons
const uint8_t Green2 = 2;
const uint8_t Red3 = 3;
// LED pins
const uint8_t SecondsLED = 12;
// Pixels controlled by a single chip
const int8_t rowsPerDisplayChip = 16;
const int8_t columnsPerDisplayChip = 8;
const int8_t xPixelBufferPadding = 8;
const int8_t yPixelBufferPadding = 8;
// Define the Minimum milliseconds before accepting another button press
const uint16_t DebounceWait = 500;
const uint16_t NotificationDisplayfor = 6000;

const uint8_t LDR0 = 0;
const uint8_t MAXOBJECTS = 8;
const uint8_t OBJECTPARMS = 10;
const uint8_t MAXBRIGHTNESS = 15;
const uint8_t MINBRIGHTNESS = 1;


// Variable Definitions

// Variables changed during Interrupts

volatile uint8_t ModeChangeRequest = 0;
volatile uint8_t BrightnessChangeRequest = 0;
volatile int8_t LocalTimeZone = -8;
volatile uint8_t invertDisplay = 0;
volatile uint8_t Tick = 0;
volatile uint8_t newSecond = 0;


uint8_t runJustTime = 1;
uint8_t runTimeTemp = 2;

// Variables Storing Current State

uint8_t DisplayMode = runJustTime;
uint8_t Brightness = 15;
uint8_t PrevBright = Brightness;
uint16_t Ambient = 0;
uint16_t PrevAmbient = 0;
QueueArray <uint16_t> AverageAmbient;

float CurrentTemp = 0;
//float BufferTemp = 0;
float PreviousTemp = 0;

//float Temperature = 0;
float PrevTemperature = 0;
//float StableTemperature = 0;
QueueArray <float> AverageTemperature;

// Status variables
uint8_t ThermometerDetected = 0;
uint8_t WipeDisplay = 0;
uint8_t ReadByte = 0;

// Configuration variables
uint8_t ENABLE_NotifyTempChange = 1;
uint8_t  ENABLE_AutoBrightness = 1;
uint8_t ENABLE_DoubleFontSize = 0;
float CFG_TempChangeDifferenceNotification = 0.5;
unsigned long CFG_TempSampleRate = 2000;
unsigned long CFG_LightSampleRate = 100;

// Variables Storing When Something Happened

unsigned long Tock = 0;
unsigned long LastMilliMode = 0;
unsigned long LastMilliDigits = 0;
unsigned long LastMilliAmbient = 0;
unsigned long LastMilliTemperature = 0;
unsigned long LastTempNotification = 0;
unsigned long LastMotion = 0;
unsigned long ModePressed = 0;
unsigned long ModeChanged = 0;
unsigned long BrightnessPressed = 0;
unsigned long BrightnessChanged = 0;

// Data wire is plugged into pin 4 on the Arduino
#define ONE_WIRE_BUS 4

TimeChangeRule *tcr;        //pointer to the time change rule, used to get TZ abbrev
time_t utc, local;          // Universal and local time in time_t format

time_t prevDisplay = 0; // when the digital clock was displayed

// GPS settings
//TinyGPS gps;
TinyGPSPlus gps;

// To use a hardware serial port, which is far more efficient than
// SoftwareSerial, uncomment this line and remove SoftwareSerial
#define SerialGPS Serial1

//  Digits to be displayed
uint8_t digits[9] = {};
// number of digits used
uint8_t digitQTY = 4;

// Start Y position
uint8_t startYposition = 8;
	
// Object used by function
uint8_t objectDigits[MAXOBJECTS] = {};
	
// Which datetime field to start displaying from
uint8_t startAtDigit = 3;
	
// Date time fields
int (* digits2Display[])(time_t) = {year, month, day ,hour, minute, second, centisecond};

// instead of wasting lots of memory with an int for each bit, use a long for each column
unsigned long PixelColumnBuffer[40];

// Keep track of how many layered objects are in the array
uint8_t ObjectQTY = 0;

// Object buffer (contains the objects to be placed in the buffer) - 9 details per object, currently max of 8 objects 
int8_t ObjectBuffer[MAXOBJECTS][OBJECTPARMS];
// Object ID, Size X, Size Y, Location X, Location Y, Object inversion, Object Function, Object pointer, Roll Sequence position, Cloned ObjectID
// Object Functions: 0=off, 1=on, 2=addition, 3=subtraction 
// Cloned ObjectID will = itself if it is a clone, otherwise will be -1

// Array of Object pointers
const uint8_t *ObjectBufferObjects[MAXOBJECTS];
	
// Refresh display from buffer
uint8_t RefreshNeeded = 1;

uint8_t Buffer2Display[4]  = {0x70,0x71,0x72,0x73};
	
// Setup a oneWire instance to communicate with any OneWire devices
DeviceAddress insideThermometer;
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

// Interrupt functions //

static void ModeChangeRequested (){
	ModeChangeRequest = 1;
}

static void BrightnessChangeRequested (){
	BrightnessChangeRequest = 1;
}

//Initializing the HT19K33 display matrix IC's
static void initDisplayMatrix(){
	for(int i = 0; i < 4; i++){
		Wire.begin();
		Wire.beginTransmission(Buffer2Display[i]);
		Wire.write(0x21);  // turn on oscillator
		Wire.endTransmission();
		Wire.beginTransmission(Buffer2Display[i]);
		Wire.write(HT16K33_BLINK_CMD | HT16K33_BLINK_DISPLAYON | (HT16K33_BLINK_OFF << 1));
		Wire.endTransmission();
		//if (b > 15) b = 15;
		Wire.beginTransmission(Buffer2Display[i]);
		Wire.write(HT16K33_CMD_BRIGHTNESS | Brightness);
		Wire.endTransmission();
	}
}

// Byte Display Functions //

// Set the PixelBuffer to contain all 0's, for the size of it
static void initPixelColumnBuffer(){
	memset(PixelColumnBuffer,0,sizeof(PixelColumnBuffer));
}

// Set the ObjectBuffer to contain all 0's, for the size of it
static void initObjectBuffer(){
	memset(ObjectBuffer,0,sizeof(ObjectBuffer));
}

// Push Pixel buffer to display
static void pushPixelColumnBuffer(){
	// If refresh needed
	if(RefreshNeeded == 0){
		return;
	}
	byte singlebyte = B00000000;
	byte singlebit = B0;
	uint8_t address;
	// loop through all columns and rows and push to display chips, this is specific to the arrangement of the chips
	// For each display chip
	for (uint8_t i = 0; i < sizeof(Buffer2Display); i++){
		// Enable the wire transmission
		address = Buffer2Display[i];
		Wire.beginTransmission(address);
		Wire.write((uint8_t)0x00);
		// For each row or y-axis, upper Matrix, then below
		for (uint8_t j = 0; j < rowsPerDisplayChip; j++){
			int interlacedRow = j;
			if ( (j % 2) == 0 ){
				// Even row
				interlacedRow = j-(j/2)+1;
			} else {
				// Odd row
				interlacedRow = j+((17-j)/2);
			}
			////Serial.println(interlacedRow);
			// For each column or x-axis, which will be read LSBF
			for (uint8_t k = 0; k < columnsPerDisplayChip; k++){
				// Allow for initial padding around matrix + however many chips have gone before, then read the bit's in reverse
				uint8_t Pixelx = ((i+1)*xPixelBufferPadding) + k;
				uint8_t Pixely = (1*yPixelBufferPadding) + interlacedRow - 1;			
				singlebit = bitRead(PixelColumnBuffer[Pixelx], Pixely);
				bitWrite(singlebyte, k, bitRead(PixelColumnBuffer[Pixelx], Pixely));
				//delay(100);
			}
			// Push byte at a time to the chip
			//Serial.println(singlebyte, BIN);
			//delay(250);
			Wire.write(singlebyte & 0xFF);
		}
		// Close the wire transmission
		Wire.endTransmission();
		// Set brightness of chip
		Wire.beginTransmission(Buffer2Display[i]);
		Wire.write(HT16K33_CMD_BRIGHTNESS | Brightness);
		Wire.endTransmission();
		PrevBright = Brightness;
	}
	RefreshNeeded = 0;
}

// Serial print the Pixel Column Buffer
static void printPixelColumnBuffer(){
	Serial.println("Pixel Column Buffer");
	for (uint8_t i = 0; i < 24; i++){
		for (uint8_t j = 0; j < 40; j++){
			Serial.print(bitRead(PixelColumnBuffer[j],i));
		}
		Serial.println();
	}
	Serial.println();
}

// Push all objects to Pixel buffer
static void pushObjects2ColumnBuffer(){

	const uint8_t *FlashedObject;
	uint8_t ObjectByte = 0;
	uint8_t ObjectBit = 0;
	uint8_t CurrentPixel = 0;
	
	// Should the Pixel array be cleared first? Lets go with yes
	initPixelColumnBuffer();
	
	// Move through object array and push to Pixel buffer
	for (int i = 0; i < ObjectQTY; i++){
		if (ObjectBuffer[i][6] != 0){		
			// Assign a pointer to the object
			FlashedObject = ObjectBufferObjects[i];
			// First through the rows or y-axis
			for (int j = 0; j < ObjectBuffer[i][2]; j++){
				ObjectByte = pgm_read_byte(&FlashedObject[j]);
				// Then through the columns of a byte or x-axis
				for (int k = 0; k < ObjectBuffer[i][1]; k++){
					ObjectBit = bitRead(ObjectByte,(ObjectBuffer[i][1]-1)-k);
					if (ObjectBuffer[i][5] == 1){
						if (ObjectBit == 1){
							ObjectBit = 0;
						} else {
							ObjectBit = 1;
						}
					}
					// Apply the object pixels to the PixelBuffer according to the object inversion and function
					switch (ObjectBuffer[i][6])	{
						case 1:
							// Function Object On
							bitWrite(PixelColumnBuffer[k+ObjectBuffer[i][3]],j+ObjectBuffer[i][4],ObjectBit);
							break;
						case 2:
							// Function Object addition
							CurrentPixel = bitRead(PixelColumnBuffer[k+ObjectBuffer[i][3]],j+ObjectBuffer[i][4]);
							bitWrite(PixelColumnBuffer[k+ObjectBuffer[i][3]],j+ObjectBuffer[i][4],(ObjectBit | CurrentPixel));
							break;
						case 3:
							// Function Object subtraction
							CurrentPixel = bitRead(PixelColumnBuffer[k+ObjectBuffer[i][3]],j+ObjectBuffer[i][4]);
							if((CurrentPixel == 1) && (ObjectBit == 1)){
								bitClear(PixelColumnBuffer[k+ObjectBuffer[i][3]],j+ObjectBuffer[i][4]);
							}
							break;
						case 4:
							// Function Object Inversion
							CurrentPixel = bitRead(PixelColumnBuffer[k+ObjectBuffer[i][3]],j+ObjectBuffer[i][4]);
							if((ObjectBit == 1) && (CurrentPixel == 0)){
								bitWrite(PixelColumnBuffer[k+ObjectBuffer[i][3]],j+ObjectBuffer[i][4],1);
							} else if((ObjectBit == 1) && (CurrentPixel == 1)){
								bitWrite(PixelColumnBuffer[k+ObjectBuffer[i][3]],j+ObjectBuffer[i][4],0);
							}
							break;
					}
				}
			}
		}
	}
}

// Add a new object to the end of the buffer if there is space and fix layer order if needed
uint8_t addNewObject(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint8_t inverted, uint8_t applyfunction, const uint8_t *object){
	// Object ID, Size X, Size Y, Location X, Location Y, Object inversion, Object Function, Object pointer, Roll Sequence position, Cloned ObjectID
	// Object Functions: 0=off, 1=on, 2=addition, 3=subtraction
	// Cloned ObjectID will = itself if it is a clone, otherwise will be -1
	
	// Count objects and compare with size of array, return NULL if no space
	if (ObjectQTY == MAXOBJECTS){ return NULL;}
	// Find last object, and populate array
	ObjectBuffer[ObjectQTY][0] = ObjectQTY;
	ObjectBuffer[ObjectQTY][1] = x;
	ObjectBuffer[ObjectQTY][2] = y;
	ObjectBuffer[ObjectQTY][3] = width;
	ObjectBuffer[ObjectQTY][4] = height;
	ObjectBuffer[ObjectQTY][5] = inverted;
	ObjectBuffer[ObjectQTY][6] = applyfunction;
	ObjectBuffer[ObjectQTY][9] = MAXOBJECTS; //Prime Object
	ObjectBufferObjects[ObjectQTY] = object;
	// Increase count of layered objects
	ObjectQTY++;
	// Return the object ID
	return ObjectQTY-1;
}

uint8_t duplicateObject(uint8_t objectID){
	// Count objects and compare with size of array, return NULL if no space
	if (ObjectQTY == MAXOBJECTS){ return NULL;}
	for (uint8_t i = 0; i < OBJECTPARMS; i++){
		ObjectBuffer[ObjectQTY][i] = ObjectBuffer[objectID][i];
	}
	ObjectBuffer[ObjectQTY][0] = ObjectQTY;
	ObjectBuffer[ObjectQTY][6] = 0;
	ObjectBufferObjects[ObjectQTY] = ObjectBufferObjects[objectID];
	// Set the clone to link to itself
	ObjectBuffer[ObjectQTY][9] = ObjectQTY;
	ObjectQTY++;
	return ObjectQTY-1;
}

// Move object to the new co-ordinates
static void moveObjectTo(uint8_t objectID, int8_t ChangeX, int8_t ChangeY){
	// Change object position by replacing values of the location
	ObjectBuffer[objectID][3] = ChangeX;
	ObjectBuffer[objectID][4] = ChangeY;
	// Set flag for display to be refreshed
	RefreshNeeded = 1;
}

// Move object by the the values specified
static void moveObjectBy(uint8_t objectID, int8_t ChangeX, int8_t ChangeY){
	// Change object position by applying the values to the location
	uint8_t CurrentX = ObjectBuffer[objectID][3];
	uint8_t CurrentY = ObjectBuffer[objectID][4];
	ObjectBuffer[objectID][3] = CurrentX + ChangeX;
	ObjectBuffer[objectID][4] = CurrentY + ChangeY;
	// Set flag for display to be refreshed
	RefreshNeeded = 1;
}

// Return x position
uint8_t objectXposition(uint8_t objectID){
	if (objectID < ObjectQTY){
		return ObjectBuffer[objectID][3];
	}
	return NULL;
}

// Return y position
uint8_t objectYposition(uint8_t objectID){
	if (objectID < ObjectQTY){
		return ObjectBuffer[objectID][4];
	}
	return NULL;
}

// Change the invert status of existing object
static void changeObjectInvertTo(uint8_t objectID, uint8_t ChangeInvertStatus){
	// Change the object invert status by overwriting the value
	ObjectBuffer[objectID][5] = ChangeInvertStatus;
	// Set flag for display to be refreshed
	RefreshNeeded = 1;
}

// Toggle the invert status of existing object
static void toggleObjectInvert(uint8_t objectID){
	// Toggle the object invert status by inverting the value
	if (ObjectBuffer[objectID][5] == 1){
		ObjectBuffer[objectID][5] = 0;	
	} else {
		ObjectBuffer[objectID][5] = 1;	
	}
	
	// Set flag for display to be refreshed
	RefreshNeeded = 1;
}

// Change the apply function of existing object
uint8_t changeObjectFunction(uint8_t objectID, uint8_t applyfunction){
	// Find the objectID
	for (uint8_t i = 0; i < ObjectQTY; i++){
		if (ObjectBuffer[i][0] == objectID){
			// Found the object, change function type
			ObjectBuffer[i][6] = applyfunction;
			// Set flag for display to be refreshed
			RefreshNeeded = 1;
			return TRUE;
		}
	}
	return NULL;
}

// Replace object with another
uint8_t replaceObject(uint8_t objectID, uint8_t SizeX, uint8_t SizeY, const uint8_t *object){
	// Find the objectID
	for (uint8_t i = 0; i < ObjectQTY; i++){
		// If object is found
		if (ObjectBuffer[i][0] == objectID){
			// Set the pointer to the object
			ObjectBufferObjects[i] = object;
			// Set the size of the replacement object
			ObjectBuffer[i][1] = SizeX;
			ObjectBuffer[i][2] = SizeY;
			// Set flag for display to be refreshed
			RefreshNeeded = 1;
			return TRUE;
		}
	}
	return NULL;
}

// Change object layer
uint8_t changeObjectLayer(uint8_t objectID, uint8_t objectLayer){
	uint8_t Found = 0;
	uint8_t TEMPObjectBuffer[ObjectQTY][OBJECTPARMS];
	const uint8_t *TEMPObjectBufferObjects[ObjectQTY];
	// Loop through Objects in buffer to look for object to remove
	for (uint8_t i = 0; i < ObjectQTY; i++){
	// Change the layer and move other layers if necessary
	
		// Destination found
		if ( i == objectLayer){
			for (uint8_t j = 0; j < OBJECTPARMS; j++){
				TEMPObjectBuffer[i][j] = ObjectBuffer[objectID][j];
			}
			TEMPObjectBufferObjects[i] = ObjectBufferObjects[objectID];
		// Normal layer, but found already
		} else if ( ( (ObjectBuffer[i][0] != objectID) || (objectLayer != i)) && Found == 1){
			 for (uint8_t j = 0; j < OBJECTPARMS; j++){
				 TEMPObjectBuffer[i][j] = ObjectBuffer[i+1][j];
			 }
			 TEMPObjectBufferObjects[i] = ObjectBufferObjects[i+1];
		// Normal layer, and not found
		} else if ( ( (ObjectBuffer[i][0] != objectID) && (objectLayer != i)) && Found != 1){
			for (uint8_t j = 0; j < OBJECTPARMS; j++){
				TEMPObjectBuffer[i][j] = ObjectBuffer[i][j];
			}
			TEMPObjectBufferObjects[i] = ObjectBufferObjects[i];
		// Source found
		} else if (ObjectBuffer[i][0] == objectID){
			Found = 1;
			for (uint8_t j = 0; j < OBJECTPARMS; j++){
				TEMPObjectBuffer[i][j] = ObjectBuffer[objectLayer][j];
			}
			TEMPObjectBufferObjects[i] = ObjectBufferObjects[objectLayer];
		}
		
	// Need to confirm destination layer is good.
	
	// Look to see if the desired Layer is occupied
	
	// If it is rebuild the array and bump everything after it
	
	// If not change the layer value, and rebuild the order based on layer
	
	}
	 if (Found == 1){
		for (uint8_t i = 0; i < ObjectQTY; i++){
			for (uint8_t j = 0; j < OBJECTPARMS; j++){
				ObjectBuffer[i][j] = TEMPObjectBuffer[i][j];
			}
			ObjectBufferObjects[i] = TEMPObjectBufferObjects[i];
		}
		// Set flag for display to be refreshed
		RefreshNeeded = 1;
		return TRUE;
	 }
	 return FALSE;
}

// Remove an object
uint8_t deleteObject(uint8_t objectID){
	uint8_t Found = 0;
	// Loop through Objects in buffer to look for object to remove
	for (uint8_t i = 0; i < ObjectQTY; i++){
		// If already found, so shuffle this up
		if (Found == 1){
			for (uint8_t j = 0; j < OBJECTPARMS; j++){
				ObjectBuffer[i][j] = ObjectBuffer[i+1][j];
			}
			ObjectBufferObjects[i] = ObjectBufferObjects[i+1];
		}
		// Found layer to delete, shuffle others
		if (ObjectBuffer[i][0] == objectID){
			Found = 1;
			for (uint8_t j = 0; j < OBJECTPARMS; j++){
				ObjectBuffer[i][j] = ObjectBuffer[i+1][j];
			}
			ObjectBufferObjects[i] = ObjectBufferObjects[i+1];
		}
	}
	// Clean up since it was found
	if (Found == 1){
		// Replace all values of object with zero
		for (uint8_t k = 0; k < OBJECTPARMS; k++){
			ObjectBuffer[ObjectQTY][k] = 0;
		}
		// Reduce count of layered objects
		ObjectQTY--;
		//Need to delete any object linked to this
		for (uint8_t m = 0; m < ObjectQTY; m++){
			if(ObjectBuffer[m][9] == objectID){
				ObjectBuffer[m][9] = MAXOBJECTS;
			}
		}
		// Set flag for display to be refreshed
		RefreshNeeded = 1;
		return TRUE;
	} else {
		return NULL;
	}
}

// If time changed update objects adjust any objects based on time change
static void pushDigits2Objects(){
	if (ENABLE_DoubleFontSize == 1){
		for (uint8_t i = 0; i < digitQTY; i++){
			if( digits[i] == 1 ){
				replaceObject(objectDigits[i],8,16,doubleone);
			} else if( digits[i] == 2 ){
				replaceObject(objectDigits[i],8,16,doubletwo);
			} else if( digits[i] == 3 ){
				replaceObject(objectDigits[i],8,16,doublethree);
			} else if( digits[i] == 4 ){
				replaceObject(objectDigits[i],8,16,doublefour);
			} else if( digits[i] == 5 ){
				replaceObject(objectDigits[i],8,16,doublefive);
			} else if( digits[i] == 6 ){
				replaceObject(objectDigits[i],8,16,doublesix);
			} else if( digits[i] == 7 ){
				replaceObject(objectDigits[i],8,16,doubleseven);
			} else if( digits[i] == 8 ){
				replaceObject(objectDigits[i],8,16,doubleeight);
			} else if( digits[i] == 9 ){
				replaceObject(objectDigits[i],8,16,doublenine);
			} else if( digits[i] == 0 ){
				replaceObject(objectDigits[i],8,16,doublezero);
			}
		}
	} else {
		for (uint8_t i = 0; i < digitQTY; i++){
			if( digits[i] == 1 ){
				replaceObject(objectDigits[i],8,8,one);
				} else if( digits[i] == 2 ){
				replaceObject(objectDigits[i],8,8,two);
				} else if( digits[i] == 3 ){
				replaceObject(objectDigits[i],8,8,three);
				} else if( digits[i] == 4 ){
				replaceObject(objectDigits[i],8,8,four);
				} else if( digits[i] == 5 ){
				replaceObject(objectDigits[i],8,8,five);
				} else if( digits[i] == 6 ){
				replaceObject(objectDigits[i],8,8,six);
				} else if( digits[i] == 7 ){
				replaceObject(objectDigits[i],8,8,seven);
				} else if( digits[i] == 8 ){
				replaceObject(objectDigits[i],8,8,eight);
				} else if( digits[i] == 9 ){
				replaceObject(objectDigits[i],8,8,nine);
				} else if( digits[i] == 0 ){
				replaceObject(objectDigits[i],8,8,zero);
			}
		}
	}
}

// Current State Functions //

// Adjust Brightness based on ambient average
void AmbientAdjust(uint16_t CurrentAmbience){
	AverageAmbient.enqueue (CurrentAmbience);
	if (AverageAmbient.count() > 6){
		AverageAmbient.dequeue();
	}
	Ambient = AverageAmbient.avgValue();
}

// Adjust Temperature based on average average
void TemperatureAdjust(uint8_t CurrentTemperature){
	AverageTemperature.enqueue (CurrentTemperature);
	if (AverageTemperature.count() > 4){
		AverageTemperature.dequeue();
	}
	//StableTemperature = AverageTemperature.avgValue();
	//CurrentTemperature;
	//Temperature = (StableTemperature);
}

// Cycle Feature Functions //

// Switch display mode
void CycleMode() {
	// If we've done this within the last 500 milliseconds then disable the flag and return
	if ( (ModePressed < ModeChanged + DebounceWait) ) {	
		ModeChangeRequest = 0;
		return; 
	}
	// Cycle through the Display Modes
	if (DisplayMode == 5){
		DisplayMode = 0;
	} else {
		DisplayMode++;
	}
	// Record the time the change was made, reset the buttons and set flags to refresh the screen
	ModeChanged = millis();
	ModeChangeRequest = 0;
	WipeDisplay = 1;
	RefreshNeeded = 1;
}

// Switch Timezone mode
void CycleTimeZone() {
	if (LocalTimeZone == 12){
		LocalTimeZone = -11;
		} else {
		LocalTimeZone++;
	}
	RefreshNeeded = 1;
}

// Switch Brightness
void CycleBrightness() {
	// If we've done this within the last 500 milliseconds then disable the flag and return
	if ( (BrightnessPressed < BrightnessChanged + DebounceWait) ) {
		BrightnessChangeRequest = 0;
		return;
	}
	if (Brightness == 15){
		Brightness = 1;
		} else {
		Brightness++;
	}
	// Record the time the change was made, reset the buttons and set flags to refresh the screen
	BrightnessChanged = millis();
	BrightnessChangeRequest = 0;
	RefreshNeeded = 1;
}

// Switch Invert screen
void CycleInvert() {
	if (invertDisplay == 0){
		invertDisplay = 1;
		} else {
		invertDisplay = 0;
	}
	RefreshNeeded = 1;
}

// Update Tock
void updateTock(){
	Tick = 1;
	newSecond = 1;
}

// Centiseconds
int centisecond (time_t displaytime){
	return (millis() - Tock)/10;
}

// Display Mode Functions //
// Simple time
void justTime(time_t displaytime){
	
	// Temp variable used to compare if change required
	uint8_t newDigit;
	
	// Update all the digits expected
	for (uint8_t i = 0; i < 2; i++){
		newDigit = (digits2Display[startAtDigit + i](displaytime) / 10) % 10;
		if (newDigit != digits[i*2]){
			digits[i*2] = newDigit;
			RefreshNeeded = 1;
		}
		newDigit = digits2Display[startAtDigit + i](displaytime) % 10;
		if (newDigit != digits[(i*2)+1]){
			digits[(i*2)+1] = newDigit;
			RefreshNeeded = 1;
		}
	}
		
	if (RefreshNeeded == 1){
		// Start the process to push to the display
		pushDigits2Objects();
		pushObjects2ColumnBuffer();
		pushPixelColumnBuffer();
	}
}

// Simple temperature
void justTemp(float displaytemp){
	
	// Update all the digits expected
	digits[0] = (int) (displaytemp / 10) % 10;
	digits[1] = (int) displaytemp % 10;
	digits[2] = (int) (displaytemp * 10) % 10;		if(PrevTemperature != displaytemp){		RefreshNeeded = 1;		PrevTemperature = displaytemp;	}
	
	if (RefreshNeeded == 1){
		// Start the process to push to the display
		pushDigits2Objects();
		replaceObject(objectDigits[3],8,16,doubledegreesc);
		pushObjects2ColumnBuffer();
		pushPixelColumnBuffer();
	}
	
}

// Bootup and Runtime Functions //
void setup() {
	Serial.begin(9600);
	
	// Initialise the display chips and flush the buffers
	initDisplayMatrix();
	initPixelColumnBuffer();
	initObjectBuffer();
		
	attachInterrupt(0, updateTock, RISING);
	attachInterrupt(1, ModeChangeRequested, RISING);

	SerialGPS.begin(9600);

	// Configure all the pins needed for the push button switches
	pinMode(Green2, INPUT);
	// Enable pullup resistor
	digitalWrite(Green2, HIGH);
	pinMode(Red3, INPUT);
	// Enable pullup resistor
	digitalWrite(Red3, HIGH);
	pinMode(LDR0, INPUT);
	pinMode(SecondsLED, OUTPUT);
	// Enable global interrupts
	sei();
	// Update matrix for the first time
	RefreshNeeded = 1;
	
	// Setup the thermometer
	sensors.begin();
	ThermometerDetected = sensors.getDeviceCount();
	if (ThermometerDetected == 1){
		if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0");
		sensors.setResolution(insideThermometer, 9);
	} else {
		//BufferTemp = NULL;
		CurrentTemp = NULL;
	}
	// Object ID, Size X, Size Y, Location X, Location Y, Object Function, Object Inversion, Object pointer
	// Object Functions: 0=off, 1=on, 2=addition, 3=subtraction, 4=inversion
	
	objectDigits[0] = {addNewObject(8,16,7,startYposition,0,2,doubleone)};
	objectDigits[1] = {addNewObject(8,16,15,startYposition,0,2,doubletwo)};
	objectDigits[2] = {addNewObject(8,16,25,startYposition,0,2,doublethree)};
	objectDigits[3] = {addNewObject(8,16,33,startYposition,0,2,doublefour)};
		
	setTime(gps.time.hour(), gps.time.minute(), gps.time.second(), gps.date.day(), gps.date.month(), gps.date.year());
}

void loop() {
	// Read GPS info
	
	//digitalWrite(SecondsLED, !digitalRead(SecondsLED));
	while (SerialGPS.available()) {
		gps.encode(SerialGPS.read());
	}
	
	if (Tick == 1){
		// Resync time based on PPS from GPS, and increment second due to parsing delay of GPS unit.
		setTime(gps.time.hour(), gps.time.minute(), gps.time.second()+1, gps.date.day(), gps.date.month(), gps.date.year());
		local = yvrTZ.toLocal(now(), &tcr);
		Tick = 0;
		Tock = millis();
	}
	
	// If the mode change button has been pressed then cycle and record when
	if (ModeChangeRequest == 1){
		ModePressed = millis();
		CycleMode();
	}
	// If the mode change button has been pressed then cycle and record when
	if (BrightnessChangeRequest == 1){
		BrightnessPressed = millis();
		CycleBrightness();
	}
	
	// If wipe display requested, clear matrix and push to driver chips
	if (WipeDisplay == 1){
		//initMatrixBuffer();
		//pushMatrixBuffer();
		WipeDisplay = 0;
		RefreshNeeded = 1;
	}
	
	// Ambient light average;
	if (ENABLE_AutoBrightness == 1 && (millis() > LastMilliAmbient + CFG_LightSampleRate) ){
		AmbientAdjust(analogRead(LDR0));
		if ( ((Ambient/22) > Brightness) && Brightness < MAXBRIGHTNESS){
			Brightness++;
			} else if ( ((Ambient/22) < Brightness) && Brightness > MINBRIGHTNESS){
			Brightness--;
		}
		LastMilliAmbient = millis();
		if (PrevBright != Brightness){
			RefreshNeeded = 1;
		}
	}
	
	startAtDigit = DisplayMode;
	
	if (ENABLE_NotifyTempChange == 1) {
		
		// If thermometer working update average temperature
		if (ThermometerDetected == 1){
			// Average Temperature;
			if (millis() > LastMilliTemperature + CFG_TempSampleRate) {
				// Send the command to get temperatures
				sensors.requestTemperatures();
				CurrentTemp = sensors.getTempC(insideThermometer);
				LastMilliTemperature = millis();
				TemperatureAdjust(CurrentTemp);
				if (PrevTemperature != AverageTemperature.avgValue()){
					RefreshNeeded = 1;
				}
			}
			// Calcuate if the temperature has changed by more that a preset value since last displayed
			float TempDiff = PrevTemperature - AverageTemperature.avgValue();
			if (TempDiff >= CFG_TempChangeDifferenceNotification || TempDiff <= -CFG_TempChangeDifferenceNotification ){
				// Stamp now as the last time changed
				LastTempNotification = millis();
				// Display the temperature
				justTemp(AverageTemperature.avgValue());
			// Show the notification for a preset period
			} else if (millis() < (LastTempNotification + NotificationDisplayfor)){
				justTemp(AverageTemperature.avgValue());
			// Don't need to show temperature, so show the time
			} else {
				justTime(local);
			}
		}
	// Thermometer not working, so just show the time
	} else {
		justTime(local);
	}
	
}